package com.rxyb.security.config;

import com.rxyb.security.handler.LoginFailHandler;
import com.rxyb.security.handler.UrlRoleAuthHandler;
import com.rxyb.security.handler.UrlRolesFilterHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import java.util.ArrayList;
import java.util.List;

/**
 * @author YJH
 * @version 1.0
 * @description security 配置
 * 注解中有三个参数，securedEnabled = true 表示方法权限控制可以使用安全注解 @Secured, 该注解的值 必须在现有的角色名称前加上ROLE前缀，如 @Secured("ROLEADMIN")，多个角色可以这样写 @Secured({"ROLEONE","ROLETWO"})，表示最少有其中一个角色才能访问。
 * <p>
 * prePostEnabled = true
 * 表示方法权限前置注解 @PreAuthorize,@PostAuthorize启用，
 * 注解 @PreAuthorize 适合进入方法之前验证授权。
 * @PreAuthorize可以兼顾，角色/登录用户权限，参数传递给方法等等 ，
 * @PreAuthorize("true") 表示允许所有访问，
 * 注解 @PostAuthorize 不经常使用，
 * 它在检查授权方法之后才被执行，
 * 所以它适合用在对返回的值作验证授权。
 * Spring EL提供可在表达式语言来访问并从方法返回 returnObject
 * 对象来反映实际的对象。@PreAuthorize单个角色可以这样写
 * @PreAuthorize("hasRole('TWO')")，多个角色可以这样写
 * @PreAuthorize("hasRole('ONE') AND hasRole('TWO')")，
 * @PreAuthorize("hasRole('ONE') OR hasRole('TWO')")注意and和or的意义。
 * AND表示拥有所有权限才能访问，OR表示拥有任意一种权限就能访问。
 * <p>
 * jsr250Enabled = true 表示 启动了JSR-250的注解支持，在方法上使用注解来控制访问权限，注解@DenyAll 拒绝所有访问，注解 @PermitAll 允许所有访问，注解 @RolesAllowed({"USER", "ADMIN"}) 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省略前缀ROLE，实际的权限可能是ROLEADMIN。
 * @date 2020/7/7 10:05
 */
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
@Slf4j
@Order(90)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsServiceImpl;

    public SecurityConfiguration(UserDetailsService userDetailsServiceImpl) {
        this.userDetailsServiceImpl = userDetailsServiceImpl;
    }

    /**
     * http 请求拦截
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http.csrf().disable();
        /* 开启授权认证 */
        http.authorizeRequests()
//                .withObjectPostProcessor(new DefaultObjectPostProcessor())
//                // url 决策
//                .accessDecisionManager(accessDecisionManager())
                .anyRequest().authenticated();
        // 默认登录页面
        http.formLogin().loginPage("/login").permitAll();
        // 登出登出
        http.logout().logoutUrl("/logout").deleteCookies("JSESSIONID").permitAll();
        // 登录失败
        http.formLogin().failureHandler(new LoginFailHandler());
        // 登录成功
        http.formLogin().successForwardUrl("/home?nameUrl=admin-role");
        // 访问权限不足页面跳转
        http.exceptionHandling().accessDeniedPage("/limit");


        /*http.authorizeRequests().antMatchers("/assets/js/**,").permitAll();*/

        //http.addFilterAfter(switchUserFilter(userDetailsService()), FilterSecurityInterceptor.class);

    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**/**,/ladmin/**/**,/system/**/**");
    }


    // 权限登录
//    @Bean
//    public SwitchUserFilter switchUserFilter(@Qualifier("userDetailsServiceImpl") UserDetailsService userDetailsService) {
//        SwitchUserFilter switchUserFilter = new SwitchUserFilter();
//        switchUserFilter.setUserDetailsService(userDetailsService);
//        switchUserFilter.setTargetUrl("/session");
//        return switchUserFilter;
//    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());
        /*auth.eraseCredentials(false);*/
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    //
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


//
//    /**
//     * AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
//     * ConsensusBased – 同意投票多于拒绝投票（忽略弃权回答）则允许访问
//     * UnanimousBased – 每个投票者选择弃权或同意则允许访问
//     *
//     * 决策管理
//     */
//    private AccessDecisionManager accessDecisionManager() {
//        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
//        decisionVoters.add(new WebExpressionVoter());
//        decisionVoters.add(new AuthenticatedVoter());
//        decisionVoters.add(new RoleVoter());
//        /* 路由权限管理 */
//        decisionVoters.add(new UrlRoleAuthHandler());
//        return new UnanimousBased(decisionVoters);
//    }
//
//    @Autowired
//    private UrlRolesFilterHandler urlRolesFilterHandler;
//
//    class DefaultObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
//        @Override
//        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//            object.setSecurityMetadataSource(urlRolesFilterHandler);
//            return object;
//        }
//    }

}
